using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using Environment = NFreeMarker.Core.Environment;

namespace NFreeMarker.Template.Utility
{
	/**
	 * A transform that captures the output of a block of FTL code and stores that in a variable.
	 *
	 * <p>As this transform is initially present in the shared variable set, you can always
	 * access it from the templates:</p>
	 *
	 * <pre>
	 * &lt;@capture_output var="captured">
	 *   ...
	 * &lt;/@capture_output>
	 * </pre>
	 *
	 * <p>And later in the template you can use the captured output:</p>
	 *
	 * ${captured}
	 *
	 * <p>This transform requires one of three parameters: <code>var</code>, <code>local</code>, or <code>global</code>.
	 * Each of them specifies the name of the variable that stores the captured output, but the first creates a
	 * variable in a name-space (as &lt;#assign>), the second creates a macro-local variable (as &lt;#local>),
	 * and the last creates a global variable (as &lt;#global>).
	 * </p>
	 * <p>In the case of an assignment within a namespace, there is an optional parameter
	 * <code>namespace</code> that indicates in which namespace to do the assignment.
	 * if this is omitted, the current namespace is used, and this will be, by far, the most
	 * common usage pattern.</p>
	 *
	 * @deprecated Use block-assignments instead, as <code>&lt;assign x>...&lt;/assign></code>.
	 *
	 * @version $Id: CaptureOutput.java,v 1.31 2004/01/06 17:06:43 szegedia Exp $
	 */
	public class CaptureOutput : ITemplateTransformModel
    {
	    public TextWriter GetWriter(TextWriter output, IDictionary<string, ITemplateModel> args)
        {
	        const string errmsg = 
                "Must specify the name of the variable in " +
                "which to capture the output with the 'var' or 'local' or 'global' parameter.";
	        if (args == null)
	        {
	            throw new TemplateModelException(errmsg);
	        }

	        bool local = false, global = false;
	        ITemplateModel nsModel = args.GetValueOrDefault("namespace");
	        object varNameModel = args.GetValueOrDefault("var");
	        if (varNameModel == null)
            {
	            varNameModel = args.GetValueOrDefault("local");
	            if (varNameModel == null)
                {
	                varNameModel = args.GetValueOrDefault("global");
	                global = true;
	            }
                else
                {
	                local = true;
	            }
	            if (varNameModel == null)
                {
	                throw new TemplateModelException(errmsg);
	            }
	        }
	        if (args.Count == 2)
            {
	            if (nsModel == null)
                {
	                throw new TemplateModelException("Second parameter can only be namespace");
	            }
	            if (local)
                {
	                throw new TemplateModelException("Cannot specify namespace for a local assignment");
	            }
	            if (global)
                {
	                throw new TemplateModelException("Cannot specify namespace for a global assignment");
	            }
	            if (!(nsModel is Environment.Namespace))
                {
	                throw new TemplateModelException("namespace parameter does not specify a namespace. It is a " + nsModel.GetType().FullName);
	            }
	        }
	        else if (args.Count != 1) throw new TemplateModelException(
	                "Bad parameters. Use only one of 'var' or 'local' or 'global' parameters.");

	        if(!(varNameModel is ITemplateScalarModel))
            {
	            throw new TemplateModelException("'var' or 'local' or 'global' parameter doesn't evaluate to a string");
	        }
	        string varName = ((ITemplateScalarModel) varNameModel).GetAsString();
	        if(varName == null) {
	            throw new TemplateModelException("'var' or 'local' or 'global' parameter evaluates to null string");
	        }

	        return new CaptureOutputWriter(
	            output, varName, local, global, nsModel);
	    }

        private class CaptureOutputWriter : TextWriter
        {
            private readonly TextWriter _output;
            private readonly string _varName;
            private readonly bool _local;
            private readonly bool _global;
            private readonly ITemplateModel _nsModel;
            private readonly StringBuilder _buf = new StringBuilder();
            private readonly Environment _env;

            public CaptureOutputWriter(
                TextWriter output,
                string varName,
                bool local,
                bool global,
                ITemplateModel nsModel)
            {
                _output = output;
                _varName = varName;
                _local = local;
                _global = global;
                _nsModel = nsModel;
                _env = Environment.GetCurrentEnvironment();
            }

            public override Encoding Encoding
            {
                get { return _output.Encoding; }
            }

            public override void Write(char[] cbuf, int off, int len)
            {
                _buf.Append(cbuf, off, len);
            }

            public override void Flush()
            {
                _output.Flush();
            }

            public override void Close()
            {
                var result = new SimpleScalar(_buf.ToString());
                try
                {
                    if (_local)
                    {
                        _env.SetLocalVariable(_varName, result);
                    }
                    else if (_global)
                    {
                        _env.SetGlobalVariable(_varName, result);
                    }
                    else
                    {
                        if (_nsModel == null)
                        {
                            _env.SetVariable(_varName, result);
                        }
                        else
                        {
                            ((Environment.Namespace) _nsModel).Put(_varName, result);
                        }
                    }
                }
                catch (InvalidOperationException e) // if somebody uses 'local' outside a macro
                { 
                    throw new IOException("Could not set variable " + _varName + ": " + e.Message);
                }
            }
        }
	}
}